The procedural programming paradigm often involves the invocation of one procedure (more commonly known as a function or method) from within another. Each invocation involves the storage of a set of information. The state of the processor (e.g., the data within the CPU registers) is stored so that the processor can be returned to this state upon completion of the invoked function. The parameters passed into the invoked function (e.g., the values of simple types such as integers and characters, and the memory addresses of larger structures, such as class instances) are stored in a predetermined order, and then loaded by the invoked function. Also stored are the memory address of the instruction to which the processor returns upon completion of the invoked function and, in some circumstances, the address of a memory buffer allocated to hold a variable or object returned by the invoked function. These data items are stored in a manner that provides precise and predictable accessibility to both the invoking and the invoked functions. This may be difficult because the invoked function may invoke others, and the nesting of invoked functions may reach many levels of depth, especially in the case of recursion, where a function invokes itself (possibly hundreds or thousands of times.)
Accordingly, procedurally oriented code often stores this information on a call stack, a designated portion of memory that holds the data exchanged between each invoking function and each invoked function. A computing environment may utilize such a call stack for storing data between such function invocations, and some such systems utilize a plurality of call stacks simultaneously for various purposes (e.g., for storing different kinds of data, and/or for storing call stack data between different processes.) Upon a function invocation, the operating environment creates a call stack frame (also known as an activation record) on the call stack, comprising the set of information described above stored in a predetermined manner and order, on the “top” of the call stack. Eventually the “called” function terminates, and processing control is returned to the invoking function, while the call stack frame is removed from the call stack. In turn, the invoked function may invoke other functions, resulting in the allocation of call stack frames “above” the call stack frame of the invoked function that are removed upon termination of the functions invoked by the invoked function. Hence, the call stack operates as a “last-in, first-out” data structure, wherein the first call stack frame “pushed” onto the call stack is the last one “popped” off of the call stack, and the second call stack frame “pushed” onto the call stack (on “top” of the first call stack frame) is removed from the call stack before the first one can be removed, etc. The computer system component that manages access to the call stack (e.g., the memory allocation in execution of the “pushing” and “popping” operations) also tracks the “top” of the call stack, i.e., the memory address of free space that may be used for the next “pushed” data. This memory address is tracked by storing it in a stack pointer, which may be (e.g.) a register, a data object stored elsewhere in system memory, etc.
Several scenarios may benefit from an analysis of the contents of the call stack. As one example, debugging a procedurally programmed application may involve suspending execution upon reaching specific instruction and examining the call stack to determine the order in which functions were called, and to detect how a particular instruction was reached. The analysis may yield information on the chronology of nested function invocations and the details thereof, such as: “Function A invoked Function B, while passing in parameters object1 and object2; Function B then invoked Function C; Function C then performed recursion with a second invocation of Function C . . . .” This analysis of the call stack, sometimes called a “stack walk” or “stack unwinding,” may involve parsing the contents of a call stack frame, and the results may be reported to the administrator or programmer, e.g., in a debugger application. In the case of call stack debugging, a virtual unwinding is conventionally performed, wherein the call stack is not altered, but where its contents are analyzed on a per-frame basis as if the calls tack frames were popped from the stack in the usual manner.